package dominio;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import util.IntegerUtils;

public abstract class Vivacidad {
	protected Integer rangoDeVision;
	
	private Map<Integer, Nodo> distanciaAVecinos;

	public Vivacidad(){
		
	}
	
	public void cambioAgresividad(){
		
	}
	
	
	public void reinicioAgresividad(){
		
	}
	
	public Integer getRangoDeVision() {
		return rangoDeVision;
	}

	public void setRangoDeVision(Integer rangoDeVision) {
		this.rangoDeVision = rangoDeVision;
	}

	public Integer getDistanciaMinimaAlPacman() {
		return IntegerUtils.getMenor(this.distanciaAVecinos.keySet());
	}
	
	public Integer getDistanciaMaximaAlPacman() {
		return IntegerUtils.getMayor(this.distanciaAVecinos.keySet());
	}
	
	public boolean estaEnBifurcacion(IMovible fantasma){
		Nodo nodoActual = fantasma.getNodoActual();
		//ver que pasa cuando cantidad de vecinos es 1!!
		if (nodoActual.getCantidadVecinos() > 2) {
			return true;			
		} else {
			return false;
		}
	}
	
	protected void moverCuandoHayDosVecinos(IMovible fantasma) {
		
		Collection<Sentido> otrosSentidos = Sentido.getTodosLosSentidos();
		otrosSentidos.remove(Sentido.getSentidoContrario(fantasma.getSentidoMovimiento()));
		Sentido nuevoSentido = getSentidoVecinoExistente(fantasma.getNodoActual(), otrosSentidos);
		
		moverseEnSentido(nuevoSentido, fantasma);
	}
	
	private Sentido getSentidoVecinoExistente(Nodo nodo, Collection<Sentido> sentidos) {
		for (Sentido s : sentidos) {
			if (nodo.getVecino(s) != null) {
				return s;
			}
		}
		return null;
	}
	
	public Integer getCantidadCaminosPosibles(IMovible fantasma){
		return fantasma.getNodoActual().getCantidadVecinos();
	}
	
	public void perseguir(IMovible fantasma, IMovible pacman) {
		if (this.estaEnBifurcacion(fantasma)) {
			this.moverSiEstaEnBifurcacion(fantasma, pacman);
		} else {
			//avanzar en el mismo sentido en el que venia si hay para avanzar, sino retroceder.
			this.moverSiNoEstaEnBifurcacion(fantasma);
		}
	}
	
	protected void moverSiEstaEnBifurcacion(IMovible fantasma, IMovible pacman) {
		cargarMapaVecinos(fantasma, pacman);
		Integer distancia = this.getDistanciaMinimaAlPacman();
		if (distancia == null) {
			moverFueraDeRango(fantasma);
		} else {
			if (distancia != 0) {
				this.perseguirPacman(fantasma);
			}
		}
	}
	
	
	private void cargarMapaVecinos(IMovible fantasma, IMovible pacman) {
		Nodo nodoPacman = pacman.getNodoActual();
		this.distanciaAVecinos = new HashMap<Integer, Nodo>();
				
		for (Nodo vecino : fantasma.getNodoActual().getVecinos()) {
			if (!vecino.equals(getNodoAnterior(fantasma))) {
				this.distanciaAVecinos.put(nodoPacman.getDistancia(vecino, this.rangoDeVision), vecino);
			}
		}
		
	}
	
	private Nodo getNodoAnterior(IMovible fantasma) {
		return fantasma.getNodoActual().getVecino(Sentido.getSentidoContrario(fantasma.getSentidoMovimiento()));
	}

	protected void moverFueraDeRango(IMovible fantasma) {
		moverAleatoriamente(fantasma);		
	}

	protected void moverSiNoEstaEnBifurcacion(IMovible fantasma){
		if (this.getCantidadCaminosPosibles(fantasma) == 1) {
			this.moverseEnSentido(Sentido.getSentidoContrario(fantasma.getSentidoMovimiento()), fantasma);
		} else {
			//avanza en el sentido que venia, si no se puede avanzar, doblar en la direccion que sea posible!!!
			this.moverCuandoHayDosVecinos(fantasma);
		}
	}
	
	public void escapar(IMovible fantasma, IMovible pacman) {
		 
		if (this.estaEnBifurcacion(fantasma)) {
			escaparCuandoEstaBifurcacion(fantasma, pacman);
		} else {
			//avanzar en el mismo sentido en el que venia si hay para avanzar, sino retroceder.
			this.moverCuandoHayDosVecinos(fantasma);
			
		}
		
	}
	
	private void escaparCuandoEstaBifurcacion(IMovible fantasma, IMovible pacman) {
		cargarMapaVecinos(fantasma, pacman);
		Integer distancia = this.getDistanciaMinimaAlPacman();
		
		if (distancia == null) {
			this.moverAleatoriamente(fantasma);
		} else {
			this.alejarse(fantasma, pacman);
		}
		
	}

	//repe
	protected void moverAleatoriamente(IMovible fantasma){
		Nodo nodoActual = fantasma.getNodoActual();
		
		Collection<Nodo> vecinos = nodoActual.getVecinos();
		vecinos.remove(getNodoAnterior(fantasma));
		
		//elegir siguienteNodo aleatoriamente
		Nodo siguienteNodo = getNodoAleatorio(vecinos);
		moverseEnSentido(nodoActual.getDireccion(siguienteNodo), fantasma);
	}
	
	protected void perseguirPacman(IMovible fantasma) {
		
		Nodo siguienteNodo = getNodoMasCercanoAlPacman();
		moverseEnSentido(fantasma.getNodoActual().getDireccion(siguienteNodo), fantasma);
		
	}
	
	protected void alejarse(IMovible fantasma, IMovible pacman){
		Nodo siguienteNodo = getNodoMasLejanoAlPacman(fantasma, pacman);
		moverseEnSentido(fantasma.getNodoActual().getDireccion(siguienteNodo), fantasma);		
	}
	
	private Collection<Nodo> getVecinosValidos(IMovible fantasma) {
		Nodo nodoActual = fantasma.getNodoActual();
		
		Collection<Nodo> vecinos = nodoActual.getVecinos();
		vecinos.remove(getNodoAnterior(fantasma));
		
		return vecinos;
	}
	
	private Nodo getNodoAleatorio(Collection<Nodo> vecinos) {
		
		Map<Integer, Nodo> mapa = new HashMap<Integer, Nodo>();
		int i = 1;
		for (Nodo vecino : vecinos) {
			mapa.put(i, vecino);
			i++;			
		}
		
		return mapa.get(IntegerUtils.getNumeroAleatorio(mapa.keySet().size()));
	}
	
	private Nodo getNodoMasCercanoAlPacman() {		
		return this.distanciaAVecinos.get(IntegerUtils.getMenor(this.distanciaAVecinos.keySet()));
	}
	
	private Nodo getNodoMasLejanoAlPacman(IMovible fantasma, IMovible pacman) {
		Collection<Nodo> vecinos = getVecinosValidos(fantasma);
		
		List<Nodo> nodosLejos = new ArrayList<Nodo>();
		for (Nodo nodo : vecinos) {
			if (!this.distanciaAVecinos.values().contains(nodo)) {
				nodosLejos.add(nodo);
			}
		}
		
		if (nodosLejos.size() > 0) {
			return nodosLejos.get(IntegerUtils.getNumeroAleatorio(nodosLejos.size()) - 1);
		} else {
			return this.distanciaAVecinos.get(getDistanciaMaximaAlPacman());
		}
		
	}
	
	protected void moverseEnSentido(Sentido sentido, IMovible fantasma) {		
		fantasma.setSentidoMovimiento(sentido);
		fantasma.setNodoActual(fantasma.getNodoActual().getVecino(sentido));
	}

	protected void iHaciaUltimoNodoVisto(Nodo ultimoNodoPacman, IMovible fantasma){
		Nodo nodoActual = fantasma.getNodoActual();
		Sentido sentidoActual = fantasma.getSentidoMovimiento();
		
		Collection<Nodo> vecinos = nodoActual.getVecinos();
		vecinos.remove(nodoActual.getVecino(sentidoActual));
		
		Nodo siguienteNodo = getNodoMasCercanoAlNodo(ultimoNodoPacman, vecinos);
		moverseEnSentido(nodoActual.getDireccion(siguienteNodo), fantasma);
	}
	
	protected Nodo getNodoMasCercanoAlNodo(Nodo nodo, Collection<Nodo> vecinos) {
		Map<Integer, Nodo> mapa = new HashMap<Integer, Nodo>();
				
		for (Nodo vecino : vecinos) {
			mapa.put(nodo.getDistancia(vecino, this.rangoDeVision), vecino);
		}
		
		return mapa.get(IntegerUtils.getMenor(mapa.keySet()));
	}

}
